/**
 * @file       godemool/hl2demo.h
 * @brief      hl2demo types and definitions.
 * @author     ben "boin" bonoeil
 * @date       2015-02
 */

/* Copyright (c) 2007-2015, ben "boin" bonoeil */

#ifndef GODEMOOL_HL2DEMO_H
#define GODEMOOL_HL2DEMO_H

#include "godemool/ftypes.h"

#include <cassert>
#include <cstdint>
#include <istream>
#include <exception>

namespace boin {
  struct Hl2Demo;
}

struct boin::Hl2Demo {
  static const char magic[8];        /**< magic identifier ("HL2DEMO").  */
  static const char csgo_gamedir[5]; /**< CS:GO game directory ("csgo"). */

  enum {
    DEM_PROTOCOL = 4,         /**< Demo protocol (major version of a sort). */
    NET_PROTOCOL = 13473,     /**< Net protocol (minor version of sort).    */
    MAX_SPLITS = 2,           /**< Max number of splits screen.             */
    NET_MAX_PAYLOAD = 262140, /**< Largest message size.                    */
    MAX_EDICT_BITS = 11,
    WEAPON_SUBTYPE_BITS = 6,
  };

  /** Demo chunk type (for demo protocol v4). */
  enum Chunk_e {
    dem_first = 0,    /**< first command (for range test). */

    dem_invalid = 0,  /**< invalid value. */
    dem_signon,       /**< startup message, process as fast as possible. */
    dem_packet,       /**< normal network packet that we stored off.     */
    dem_synctick,     /**< sync client clock to demo tick.               */
    dem_consolecmd,   /**< console command.                              */
    dem_usercmd,      /**< user input command.                           */
    dem_datatables,   /**< network data tables.                          */
    dem_stop,         /**< end of time.                                  */
    dem_customdata,   /**< binary data (callback dependant).             */
    dem_stringtables, /**< dem_stringtable.                              */

    /* !!! always last !!! */
    dem_last          /**< Last command (for range test).                */
  };
  static const char * ChunkName(Chunk_e id);

  template <class T, typename U> struct Streamable : public T {
    Streamable<T,U>() {} // ? default call parent constructor  ?
    Streamable<T,U>(const U v) : T(v) {}
    Streamable<T,U>(const U & v) : T(v) {}
    Streamable<T,U>(std::istream & is) { Read(is); }
    void Read(std::istream & is);
  };

  typedef boin::Vector Vector;
  typedef boin::Qangle Qangle;
  typedef Streamable<boin::FInt8,uint8_t> FInt8;
  typedef Streamable<boin::FInt32,uint32_t> FInt32;
  typedef Streamable<boin::FFloat,float> FFloat;
  typedef Streamable<boin::FVector,Vector> FVector;
  typedef FVector FQangle;

  /** Demo File header. */
  struct FHeader {
    FHeader(std::istream & is);
    void Read(std::istream & is);
    char magic[8];        /**< Should be dem_magic "Hl2DEMO".        */
    FInt32 dem_ver;       /**< Should be DEMO_PROTOCOL (4).          */
    FInt32 net_ver;       /**< Something near NET_PROTOCOL.          */
    char sv_name[260];    /**< Server name (the game was played in). */
    char tv_name[260];    /**< Client name (recorder).               */
    char mp_name[260];    /**< Map name.                             */
    char gm_name[260];    /**< game directory (com_gamedir).         */
    FFloat pb_time;       /**< Time of track (seconds).              */
    FInt32 pb_tick;       /**< # of ticks in track.                  */
    FInt32 pb_fram;       /**< # of frames in track.                 */
    FInt32 sig_len;       /**< length of signondata in bytes.        */
  };

  /** Demo decoded header. */
  struct Header {
    Header();
    Header(const FHeader & fh);
    int   dem_ver;         /**< Should be DEMO_PROTOCOL (4).          */
    int   net_ver;         /**< Something near NET_PROTOCOL.          */
    char  sv_name[260];    /**< Server name (the game was played in). */
    char  tv_name[260];    /**< Client name (recorder).               */
    char  mp_name[260];    /**< Map name.                             */
    char  gm_name[260];    /**< game directory ("csgo").              */
    float pb_time;         /**< Time of track (seconds).              */
    unsigned int pb_tick;  /**< # of ticks in track.                  */
    unsigned int pb_fram;  /**< # of frames in track.                 */
    unsigned int sig_len;  /**< length of sigondata in bytes.         */
  };

  struct FSplit {
    FSplit() {};
    FSplit(std::istream & is) { Read(is); }
    void Read(std::istream & is);
    boin::FInt32 flags; /**< Flags.                       */
    boin::FVector vo1;  /**< original view origin.        */
    boin::FQangle va1;  /**< original view angles.        */
    boin::FQangle la1;  /**< original local view angles.  */
    boin::FVector vo2;  /**< resampled view origin.       */
    boin::FVector va2;  /**< resampled view angles.       */
    boin::FVector la2;  /**< resampled local view angles. */
    operator char * () { return flags; }
  };

  struct Split {
    Split() {};
    Split(const FSplit & split);

    enum {
      FDEMO_NORMAL      = 0,
      FDEMO_USE_ORIGIN2 = (1<<0),
      FDEMO_USE_ANGLES2 = (1<<1),
      FDEMO_NOINTERP    = (1<<2), /* don't interp betw this and last view */
    };
    unsigned int flags; /**< see FDEMO_ enums */

    Vector vo1;  /**< original view origin.       */
    Qangle va1;  /**< original view angles.       */
    Qangle la1;  /**< original local view angles. */
    Vector vo2;  /* resampled view origin.        */
    Qangle va2;  /* resampled view angles.        */
    Qangle la2;  /* resampled local view angles.  */
  };

  struct FCmdInfo {
    FCmdInfo(std::istream & is);
    void Read(std::istream & is);
    FSplit split[MAX_SPLITS];
  };

  struct CmdInfo {
    CmdInfo() {}
    CmdInfo(const FCmdInfo & fci);
    Split split[MAX_SPLITS];
  };

  struct CmdData {
    ~CmdData() { if (data) delete [] data; }
    CmdData() : max(0), size(0), data(nullptr) {}
    CmdData(std::istream & is, bool skip = false);
    CmdData(CmdData & cmddata) { *this = cmddata; }
    CmdData & operator = (CmdData & cmddata);
    void Alloc(size_t bytes);
    void Clear() { size = 0; }
    void Read(std::istream & is, bool skip = false);
    operator char *() const { return (char *) data; }
    operator void *() const { return (void *) data; }
    operator uint8_t *() const { return data; }
    operator int () const { return size; }

    uint32_t  max;
    uint32_t  size;
    uint8_t * data;
  };

  struct Chunk {
    struct Chunk * nxt;           /**< Next chunk.           */
    uint32_t idx;                 /**< Message ID.           */
    uint32_t tick;                /**< Tick number.          */
    Chunk_e type;                 /**< Chunk type .          */
    uint8_t slot;                 /**< Player slot.          */
    CmdInfo cmdinfo;              /**< Command Info.         */
    uint32_t seq_inp;             /**< Sequence Input ID.    */
    uint32_t seq_out;             /**< Sequence Output ID.   */
    uint32_t pos;                 /**< Position in file.     */
    CmdData data;                 /**< Command data buffer.  */
    Chunk();
    void Clear();
    bool IsValid() const { return type != dem_invalid; }
    bool IsStop() const { return type == dem_stop; }
    const char * TypeName() const { return ChunkName(type); }
  };

  Header header;
  int chkcount;
  Chunk * chkhead, * chktail, chunk;

  Hl2Demo();
  Hl2Demo(std::istream & is);
  ~Hl2Demo();

  struct WalkFlags {
    enum {
      SKIPDATA = 1, STORE = 2
    };
    WalkFlags() { skip_data = 0; }
    WalkFlags(int bits) {
      skip_data = !!(bits & SKIPDATA);
      store     = !!(bits & STORE);
    }
    unsigned int skip_data:1;
    unsigned int store:1;

  };

  Chunk * Walk(std::istream & is, const WalkFlags flags = 0);

protected:
  void FreeChunks();
  Chunk * QueueChunk();
};

#endif

/* Local Variables:  */
/* mode: c++         */
/* End:              */
